﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  JoinNowAbstractService.cs
//    
// Abstract:
//
// Abstraction for the workflow service classes that implement 
// Join Now functionality.  This module also includes some
// helper classes for storing conferencing data.
//  
//--

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using Microsoft.OfficeCommunicationsServer.Applications.Common;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Collaboration.AudioVideo;
using Microsoft.Rtc.Collaboration.Conferencing;
using Microsoft.Rtc.Signaling;

namespace Microsoft.EnhancedReminders.Dispatcher.Workflow
{
    /// <summary>
    /// Internal class used for storing conferencing information to be passed around
    /// by asynchronous operations.
    /// </summary>
    internal class ConferencingInformation
    {
        internal Call _call;
        internal CalendarNotificationItem _notificationItem;
        internal SignalingSession _signalingSession;
        internal Guid _jobId;
        internal Context _context;

        /// <summary>
        /// Ctor.
        /// </summary>
        internal ConferencingInformation(Context context, Call call, CalendarNotificationItem notificationItem, Guid jobId)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(call != null, "call != null");
            Debug.Assert(call.Conversation != null, "conversation != null");
            Debug.Assert(notificationItem != null, "notificationItem != null");
            Debug.Assert(!String.IsNullOrEmpty(notificationItem.ReminderItem.ConferenceAddress), "conferenceAddress");

            _context = context;
            _call = call;
            _notificationItem = notificationItem;
            _jobId = jobId;
        }

        internal bool IsLiveMeeting
        {
            get
            {
                Debug.Assert(_notificationItem != null, "_notificationItem");
                Debug.Assert(_notificationItem.ReminderItem != null, "_notificationItem.ReminderItem");
                Debug.Assert(_notificationItem.ReminderItem.ConferenceType == ConferenceType.LiveMeeting ||
                             _notificationItem.ReminderItem.ConferenceType == ConferenceType.ConferenceCall,
                             "Unexpected conference type");

                return _notificationItem.ReminderItem.ConferenceType == ConferenceType.LiveMeeting;
            }
        }
    }

    /// <summary>
    /// Internal class used for storing conferencing information to be passed around
    /// by asynchronous operations.
    /// </summary>
    internal class VoiceConferencingInformation
    {
        internal Context _context;
        internal AudioVideoCall _call;
        internal AudioVideoMcuSession _mcu;
        internal Conversation _conversation;
        internal ConferenceSession _session;
        internal Guid _jobId;

        internal VoiceConferencingInformation(Context context, AudioVideoCall call, AudioVideoMcuSession mcu, Conversation conversation, ConferenceSession session, Guid jobId)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(call != null, "call != null");
            Debug.Assert(mcu != null, "mcu != null");
            Debug.Assert(conversation != null, "conversation != null");
            Debug.Assert(session != null, "session != null");

            _context = context;
            _call = call;
            _mcu = mcu;
            _conversation = conversation;
            _session = session;
            _jobId = jobId;
        }
    }

    /// <summary>
    /// Data store for information relevant to the WorkflowInstance that requested
    /// a JoinNow operation.
    /// </summary>
    internal class JoinNowSubscription
    {
        internal Guid _workflowInstanceId;
        internal IComparable _queueName;

        internal JoinNowSubscription(Guid workflowInstanceId, IComparable queueName)
        {
            _workflowInstanceId = workflowInstanceId;
            _queueName = queueName;
        }
    }

    /// <summary>
    /// Abstract WorkflowService class for managing JoinNow behavior.
    /// </summary>
    public abstract class JoinNowAbstractService : WorkflowRuntimeService
	{
        private Dictionary<string, JoinNowSubscription> _subscriptions;
        private readonly object _syncObj = new object();

        /// <summary>
        /// ctor.
        /// </summary>
        protected JoinNowAbstractService()
        {
            _subscriptions = new Dictionary<string, JoinNowSubscription>();
        }

        /// <summary>
        /// Begins running the asynchronous operations necessary for join now.
        /// </summary>
        /// <param name="queueName">The name of the queue to notify when the join now operation is complete</param>
        /// <param name="call">The existing call on which to send the join now invitation.</param>
        /// <param name="notification">The notification containing the join now information</param>
        public Guid RegisterJoinNowOperation(IComparable queueName, Call call, CalendarNotificationItem notification)
        {
            Debug.Assert(call != null, "call != null");
            Debug.Assert(notification != null, "notification != null");
            Debug.Assert(!string.IsNullOrEmpty(notification.ReminderItem.ConferenceAddress), "string.IsNullOrEmpty(conferenceAddress)");

            Guid jobGuid = new Guid();

            lock (_syncObj)
            {
                _subscriptions.Add(jobGuid.ToString(), new JoinNowSubscription(WorkflowEnvironment.WorkflowInstanceId, queueName));
            }

            JoinConference(jobGuid, call, notification);

            return jobGuid;
        }

        /// <summary>
        /// Unregisters an instance of JoinNowInstantMessageActivity from the service.
        /// </summary>
        public void UnregisterJoinNowOperation(Guid subscriptionId)
        {
            Logger.WriteTraceInformation("Unregistering subscription {0}", subscriptionId.ToString());

            lock (_syncObj)
            {
                if (_subscriptions.ContainsKey(subscriptionId.ToString()))
                {
                    _subscriptions.Remove(subscriptionId.ToString());
                }
                else
                {
                    Logger.WriteTraceWarning("Warning: JoinNowInstantMessageService subscription key '{0}' not found during unsubscribe.", subscriptionId.ToString());
                }
            }
        }

        /// <summary>
        /// Enqueues an item for a workflow instance to know its join now operation is done.
        /// </summary>
        protected void NotifyWorkflowOfCompletedEvent(Guid jobId)
        {
            Logger.WriteTraceInformation("Notifying workflow of completed voice Join Now operation.");
            JoinNowSubscription subscription;

            lock (_syncObj)
            {
                Debug.Assert(_subscriptions.ContainsKey(jobId.ToString()), "_subscriptions.ContainsKey(jobId.ToString())");
                subscription = _subscriptions[jobId.ToString()];
            }

            try
            {
                WorkflowInstance workflowInstance = Runtime.GetWorkflow(subscription._workflowInstanceId);
                workflowInstance.EnqueueItem(subscription._queueName, EventArgs.Empty, null /* pendingWork */, null /* workItem */);
            }
            catch (InvalidOperationException)
            {
                // swallow the invalid operation; this is most likely thrown because the workflow has ended due to the
                // transfer, so we no longer can notifiy workflow that we're done. In that case we have nothing
                // to worry about, but we should clean up the workflow subscription.
                Logger.WriteTraceWarning(Context.Global, "Workflow ended before join now completed. This isn't a problem but should be investigated.");
                
                lock (_syncObj)
                {
                    if (_subscriptions.ContainsKey(jobId.ToString()))
                    {
                        _subscriptions.Remove(jobId.ToString());
                    }
                    else
                    {
                        Logger.WriteTraceWarning("Warning: JoinNowInstantMessageService subscription key '{0}' not found during unsubscribe.", jobId.ToString());
                    }
                }

            }
            catch (Exception e)
            {
                Logger.WriteTraceError(Context.Global, e, "Error writing to workflow queue for Join Now Activity.");
                Helpers.FilterIrrecoverableExceptions(e);
            }
        }

        /// <summary>
        /// Begins the asynchronous join now operations.
        /// </summary>
        protected abstract void JoinConference(Guid jobId, Call erCall, CalendarNotificationItem notification);
	}
}
